﻿namespace MicroCloud.Identity
{
    /// <summary>
    /// 在线用户信息提供者
    /// </summary>
    /// <typeparam name="TUser">用户实体类型</typeparam>
    /// <typeparam name="TUserKey">用户编号类型</typeparam>
    /// <typeparam name="TUserClaim">用户声明实体类型</typeparam>
    /// <typeparam name="TUserClaimKey">用户声明编号类型</typeparam>
    /// <typeparam name="TRole">角色实体类型</typeparam>
    /// <typeparam name="TRoleKey">角色编号类型</typeparam>
    /// <typeparam name="TOrganization">组织机构实体类型</typeparam>
    /// <typeparam name="TOrganizationKey">组织机构编号类型</typeparam>
    public class OnlineUserProvider<TUser, TUserKey, TUserClaim, TUserClaimKey, TRole, TRoleKey, TOrganization, TOrganizationKey> : IOnlineUserProvider
        where TUser : UserBase<TUserKey>
        where TUserKey : struct, IEquatable<TUserKey>
        where TUserClaim : UserClaimBase<TUserClaimKey, TUserKey>
        where TUserClaimKey : struct, IEquatable<TUserClaimKey>
        where TRole : RoleBase<TRoleKey, TUserKey>
        where TRoleKey : struct, IEquatable<TRoleKey>
        where TOrganization : OrganizationBase<TOrganizationKey, TUserKey>
        where TOrganizationKey : struct, IEquatable<TOrganizationKey>
    {
        //字段
        private readonly IServiceProvider _serviceProvider;
        private readonly IDistributedCache _cache;
        private readonly AsyncLocker _asyncLock = new();

        #region "构造函数"
        #region "初始化一个在线用户信息提供者的新实例"
        /// <summary>
        /// 初始化一个在线用户信息提供者 <see cref="OnlineUserProvider{TUser, TUserKey, TUserClaim, TUserClaimKey, TRole, TRoleKey, TOrganization, TOrganizationKey}"/> 的新实例
        /// </summary>
        /// <param name="serviceProvider"></param>
        public OnlineUserProvider(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
            _cache = serviceProvider.GetService<IDistributedCache>();
        }
        #endregion

        #endregion

        #region "方法"
        #region "获取或创建在线用户信息"
        /// <summary>
        /// 获取或创建在线用户信息
        /// </summary>
        /// <param name="userId">用户编号</param>
        /// <returns>在线用户信息</returns>
        public virtual async Task<OnlineUser> GetOrCreate(string userId)
        {
            string key = GetKey(userId);

            DistributedCacheEntryOptions options = new();
            options.SetSlidingExpiration(TimeSpan.FromMinutes(30));
            using (await _asyncLock.LockAsync())
            {
                return await _cache.GetAsync(key,
                    async () =>
                    {
                        UserManager<TUser> userManager = _serviceProvider.GetRequiredService<UserManager<TUser>>();
                        TUser user = await userManager.FindByIdAsync(userId);
                        if (user == null)
                        {
                            return null;
                        }

                        //处理用户角色
                        IList<string> roles = await userManager.GetRolesAsync(user);
                        RoleManager<TRole> roleManager = _serviceProvider.GetRequiredService<RoleManager<TRole>>();
                        var onlineUserRoles = roleManager.Roles.Where(m => roles.Contains(m.Name)).Select(o => new OnlineRole { Id = o.Id.ToString(), Name = o.Name, Remark = o.Remark, IsAdmin = o.IsAdmin }).ToList();
                        bool isAdmin = roleManager.Roles.ToList().Exists(m => roles.Contains(m.Name) && m.IsAdmin);
                        RefreshToken[] refreshTokens = await GetRefreshTokens(user);

                        OnlineUser onlineUser = new()
                        {
                            Key = key,
                            UserId = user.Id.ToString(),
                            UserName = user.UserName,
                            NickName = user.NickName,
                            Email = user.Email,
                            PhoneNumber = user.PhoneNumber,

                            IsAdmin = isAdmin,

                            Roles = [.. onlineUserRoles],
                            RefreshTokens = refreshTokens.ToDictionary(m => m.ClientId, m => m),
                        };

                        // UserClaim都添加到扩展数据
                        IList<Claim> claims = await userManager.GetClaimsAsync(user);
                        foreach (Claim claim in claims)
                        {
                            onlineUser.ExtendData.Add(claim.Type, claim.Value);
                        }

                        return onlineUser;
                    },
                    options);
            }
        }
        #endregion
        #region "移除在线用户信息"
        /// <summary>
        /// 移除在线用户信息
        /// </summary>
        /// <param name="userIds">用户编号</param>
        public virtual void Remove(params string[] userIds)
        {
            Check.NotNull(userIds, nameof(userIds));
            foreach (string userId in userIds)
            {
                string key = GetKey(userId);
                _cache.Remove(key);
            }
        }
        #endregion

        #endregion

        #region "私有方法"
        #region "获取指定用户所有刷新Token"
        /// <summary>
        /// 获取指定用户所有刷新Token
        /// </summary>
        /// <param name="user">用户对象</param>
        /// <returns>刷新Token</returns>
        private async Task<RefreshToken[]> GetRefreshTokens(TUser user)
        {
            if (_serviceProvider.GetService<IUserStore<TUser>>() is not IMicroCloudUserAuthenticationTokenStore<TUser> store)
            {
                return Array.Empty<RefreshToken>();
            }
            //const string loginProvider = "JwtBearer";
            string[] jsons = await store.GetTokensAsync(user, JwtConstants.Provider, CancellationToken.None);
            if (jsons.Length == 0)
            {
                return Array.Empty<RefreshToken>();
            }

            RefreshToken[] tokens = jsons.Select(m => m.FromJsonString<RefreshToken>()).ToArray();
            RefreshToken[] expiredTokens = tokens.Where(m => m.ExpirationTimeUtc < DateTime.UtcNow).ToArray();
            if (expiredTokens.Length <= 0)
            {
                return tokens;
            }
            tokens = tokens.Except(expiredTokens).ToArray();

            #region "删除过期的Token：频繁请求会导致进程阻塞，暂注释，使用后台任务定时执行清除。"
            //try
            //{
            //    //TODO：频繁请求会导致事务进程与另一个进程被死在 锁 资源上，故使用try包裹一下。
            //    UserManager<TUser> userManager = _serviceProvider.GetService<UserManager<TUser>>();
            //    IUnitOfWork unitOfWork = _serviceProvider.GetUnitOfWork(true);
            //    foreach (RefreshToken expiredToken in expiredTokens)
            //    {
            //        await userManager.RemoveRefreshTokenAsync(user, expiredToken.ClientId);
            //    }
            //    await unitOfWork.CommitAsync();
            //}
            //catch (Exception ex)
            //{
            //    var logger = _serviceProvider.GetLogger(GetType());
            //    logger.LogError(ex, ex.Message);
            //}
            #endregion

            return tokens;
        }
        #endregion

        #endregion

        #region "私有静态方法"
        #region "获取在线用户缓存键"
        /// <summary>
        /// 获取在线用户缓存键
        /// </summary>
        /// <param name="userKey">用户键</param>
        /// <returns>在线用户缓存键</returns>
        private static string GetKey(string userKey)
        {
            return $"Identity:OnlineUser:{userKey}";
        }
        #endregion

        #endregion

    }

}
